package com.leyou.trade.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leyou.common.exceptios.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.trade.dto.OrderFormDTO;
import com.leyou.trade.entity.Order;
import com.leyou.trade.entity.OrderDetail;
import com.leyou.trade.entity.OrderLogistics;
import com.leyou.trade.entity.enums.OrderStatus;
import com.leyou.trade.mapper.OrderMapper;
import com.leyou.trade.service.OrderDetailService;
import com.leyou.trade.service.OrderLogisticsService;
import com.leyou.trade.service.OrderService;
import com.leyou.trade.utils.PayHelper;
import com.leyou.trade.utils.UserHolder;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.AddressDTO;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.common.constants.MQConstants.ExchangeConstants.ORDER_EXCHANGE_NAME;
import static com.leyou.common.constants.MQConstants.RoutingKeyConstants.EVICT_ORDER_KEY;
import static com.leyou.trade.constants.PayConstants.ORDER_NO_KEY;
import static com.leyou.trade.constants.PayConstants.TOTAL_FEE_KEY;

/**
 * @author 虎哥
 */
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    private final ItemClient itemClient;

    private final OrderDetailService detailService;

    private final UserClient userClient;

    private final OrderLogisticsService logisticsService;

    private final PayHelper payHelper;

    private final AmqpTemplate amqpTemplate;

    public OrderServiceImpl(ItemClient itemClient, OrderDetailService detailService, UserClient userClient, OrderLogisticsService logisticsService, PayHelper payHelper, AmqpTemplate amqpTemplate) {
        this.itemClient = itemClient;
        this.detailService = detailService;
        this.userClient = userClient;
        this.logisticsService = logisticsService;
        this.payHelper = payHelper;
        this.amqpTemplate = amqpTemplate;
    }

    @Override
    @Transactional
    public Long createOrder(OrderFormDTO orderFormDTO) {
        // 1.新增订单
        Order order = new Order();
        // 1.1.金额部分
        order.setPaymentType(orderFormDTO.getPaymentType());
        Map<Long, Integer> carts = orderFormDTO.getCarts();
        // 1.1.1.查询商品信息
        ArrayList<Long> idList = new ArrayList<>(carts.keySet());
        List<SkuDTO> skuList = itemClient.querySkuByIds(idList);
        // 1.1.2.计算总价
        long total = 0L;
        for (SkuDTO sku : skuList) {
            Integer num = carts.get(sku.getId());
            total += sku.getPrice() * num;
        }
        order.setTotalFee(total);
        // 需要结合商品信息、收货人信息、运费模板计算，其它团队提供的接口，此处全场包邮
        order.setPostFee(0L);
        // 实付金额 = 商品总价 + 邮费 - 优惠金额，优惠金额是其它团队提供的接口，此处没有优惠
        order.setActualFee(order.getTotalFee() + order.getPostFee()/* - 优惠金额*/);

        // 1.2.用户信息
        Long userId = UserHolder.getUser();
        order.setUserId(userId);

        // 1.3.订单状态和时间
        order.setStatus(OrderStatus.INIT);
        // 1.4.写入数据库
        save(order);

        Long orderId = order.getOrderId();

        // 2.新增订单详情
        List<OrderDetail> details = new ArrayList<>();
        // 2.1.遍历sku集合，把sku转为OrderDetail
        for (SkuDTO sku : skuList) {
            OrderDetail detail = new OrderDetail();
            details.add(detail);
            detail.setSkuId(sku.getId());
            detail.setPrice(sku.getPrice());
            detail.setOrderId(orderId);
            detail.setNum(carts.get(sku.getId()));
            detail.setImage(StringUtils.substringBefore(sku.getImages(), ","));
            detail.setSpec(sku.getSpecialSpec());
            detail.setTitle(sku.getTitle());
        }
        // 2.2.批量新增
        detailService.saveBatch(details);

        // 3.新增订单物流
        // 3.1.获取物流地址
        Long addressId = orderFormDTO.getAddressId();
        // 3.2.查询物流数据
        AddressDTO address = userClient.queryAddressById(addressId);
        // 3.3.检查用户id是否一致
        /*if(userId.equals(address.getUserId())){
            // 用户信息不一致，请求参数有误
            throw new LyException(400, "用户物流地址不存在！");
        }*/
        // 3.4.把address数据封装OrderLogistics对象
        OrderLogistics logistics = address.toEntity(OrderLogistics.class);
        logistics.setOrderId(orderId);
        // 3.5.写入数据库
        logisticsService.save(logistics);

        // 4.扣减库存
        try {
            itemClient.deductStock(carts);
        } catch (FeignException e) {
            // 异常继续抛出，获取状态码，和异常消息，再次封装
            throw new LyException(e.status(), e.contentUTF8());
        }
        // 5.发送清理订单消息
        amqpTemplate.convertAndSend(ORDER_EXCHANGE_NAME, EVICT_ORDER_KEY, orderId);
        // 6.返回订单id
        return orderId;
    }

    @Override
    public String getPayUrl(Long orderId) {
        // 根据id查询订单
        Order order = getById(orderId);
        // 判断是否存在
        if (order == null) {
            throw new LyException(400, "订单编号错误，订单不存在！");
        }
        // 判断订单状态是否是未付款
        if (order.getStatus() != OrderStatus.INIT) {
            // 订单已经关闭或者已经支付，无需再次获取支付链接
            throw new LyException(400, "订单已经支付或者关闭！");
        }
        // TODO 尝试读取redis中的支付url

        // 获取订单金额
        Long actualFee = order.getActualFee();
        // 统一下单，获取支付链接
        String url = payHelper.unifiedOrder(orderId, actualFee);

        // TODO 把支付的url缓存在redis中，半小时有效期

        // 返回支付链接
        return url;
    }

    @Override
    public void handleWxNotify(Map<String, String> data) {
        // 1.状态校验
        payHelper.checkReturnCode(data);
        // 2.业务结果校验
        payHelper.checkResultCode(data);
        // 3.签名校验
        payHelper.isSignatureValid(data);
        // 4.订单金额校验
        // 4.1.获取订单id和订单金额
        String orderNoStr = data.get(ORDER_NO_KEY);
        String totalFeeStr = data.get(TOTAL_FEE_KEY);
        // 4.2.判断是否存在
        if (StringUtils.isBlank(orderNoStr) || StringUtils.isBlank(totalFeeStr)) {
            throw new RuntimeException("回调参数有误！");
        }
        // 4.3.查询数据库中的订单
        Order order = getById(Long.valueOf(orderNoStr));
        // 4.4.判断金额
        if (!totalFeeStr.equals(order.getActualFee().toString())) {
            // 订单金额有误
            throw new RuntimeException("订单金额有误！");
        }

        // 5.幂等校验，判断订单状态是否已经处理过
        if (order.getStatus() != OrderStatus.INIT) {
            // 订单已经处理过了，是重复订单，消息不处理
            log.error("订单{}已经处理，丢弃重复消息", order.getOrderId());
            return;
        }

        // 6.更新订单状态
        // update tb_order set status = 2, pay_time = NOW() where order_id = 1 AND status = 1
        update().set("status", OrderStatus.PAY_UP.getValue())
                .set("pay_time", new Date())
                .eq("order_id", order.getOrderId())
                // 通过乐观锁进一步保证幂等效果
                .eq("status", OrderStatus.INIT.getValue())
                // 执行update
                .update();
        log.info("处理回调成功，订单id：{}", order.getOrderId());
    }

    @Override
    @Transactional
    public void evictOrderIfNecessary(Long orderId) {
        // 1.查询订单
        Order order = getById(orderId);
        if(order == null){
            // 订单不存在，无需处理
            return;
        }
        // 2.判断订单是否支付
        if(order.getStatus() != OrderStatus.INIT){
            // 订单已处理，无需重复处理
            return;
        }
        // 3.如果未支付，需要关闭订单
        boolean boo = update().set("status", OrderStatus.CLOSED.getValue())
                .set("close_time", new Date())
                .eq("order_id", orderId)
                // 通过乐观锁进一步保证幂等效果
                .eq("status", OrderStatus.INIT.getValue())
                // 执行update
                .update();
        if(!boo){
            // 更新失败，订单状态已经改变，无需处理
            return;
        }
        log.info("已关闭超时未支付订单：{}", orderId);
        // 4.查询OrderDetail
        List<OrderDetail> details = detailService.query().eq("order_id", orderId).list();

        // 5.获取商品及商品数量信息
        Map<Long, Integer> map = details.stream()
                .collect(Collectors.toMap(OrderDetail::getSkuId, OrderDetail::getNum));

        // 6.恢复库存
        itemClient.addStock(map);
    }
}